//Justin Ball
//James Sundstrom
//CS442

#include <iostream>		
#include <stdio.h>	
#include <string.h>
#include <netinet/in.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <dirent.h>
#include <openssl/md5.h>
#include <list>
#include <deque>
#include <cstdlib>
#include <fcntl.h>
#include "SendTo.h"
#include "myPacket.h"

using namespace std;

int sock; //Global to store the socket descriptor.

const int BUFSIZE=1024;

void sendPacket(myPacket packet, sockaddr *to) {

    uint8_t pookie[16];

    //Add the checksum to the packet.
    computeMD5(&packet, pookie);
    memcpy(packet.checksum, pookie, 16);

    int ret = SendTo(sock, &packet, MY_PACKET_SIZE, 0, to, sizeof(*to));
    if(ret == -1) {

	perror("Send packet failed");
	exit(1);
    }

    return;

}

/* Given a bitmap of recieved/acked packets, decides where the window should go next. */
int advanceWindow(int bitmap[], int num_packets, int window_pos, int window_size) {
	
	if((window_pos == num_packets - 1) && (bitmap[window_pos] == 1)) {
		return window_pos + 1;
	}
	
	
	int i;
	for (i = window_pos; i < num_packets; i++) {
		if(bitmap[i] == 0)
			return i;
	}
	return i;
}

void sendDir( myPacket* packet, sockaddr *to ) {

    	struct dirent *dp;
	DIR *DirectoryPointer = opendir(".");
	dp = readdir(DirectoryPointer);

	char dirbuffer[4096];
	strcpy(dirbuffer, "");

	//Struct to fill in from recvfrom()
	struct sockaddr_in from; 
	unsigned int from_len;

	//Check the packet for corruption.
	if( !checkMD5(packet) ) {

	    cout << "Handshake request is corrupt." << endl;
	    return; //Drop this packet and go back to the main loop.
	}

	//Loop through the directory entries until NULL.
	while (dp != 0)
	{
		//Get the current directory entry name and add it to the buffer with a newline.
		char* direntry;
		direntry = dp->d_name;

		//Filter out "." and ".." entries.
		if( strcmp(direntry, "..") != 0 && strcmp(direntry, ".") != 0 ) {
		    strcat(direntry, "\n");
		    strcat(dirbuffer, direntry);
		}

		//Fetch the next directory entry.    
		dp = readdir(DirectoryPointer);
        }

	//Copy the incoming and flip on the ACK bit to make an ack packet to send back.
	myPacket ack = *packet;
	ack.flags = (ack.flags | ACK);

	int dirlen = strlen(dirbuffer);
	int num_packets;

	//Determine how many packets to send.
	if((dirlen % 1024) == 0) {
	    num_packets = (dirlen / 1024);	    
	} else { 
	    num_packets = ((dirlen / 1024) + 1);
	}

	//Set up the ack packet for the dir handshake and send it.
	ack.num_packets = num_packets;
	sendPacket(ack, to);
	
	//Create structs for select.
	fd_set fds;
	struct timeval tv;

	char buf[MY_PACKET_SIZE];

	int current_pos = 0;
	int resends = 0;    

	//Loop until all dir packets have been sent.
	while(current_pos < num_packets) {

		//Set the timeout to 100 ms.
		tv.tv_sec = 0;
		tv.tv_usec = 100000;

		//Add the sock FD to the fd set for select.
		FD_ZERO(&fds);
		FD_SET(sock, &fds);

		int payload_len = 1024;

		//Calculate the payload length of the last packet.
		if ( current_pos == (num_packets - 1) ) {

		    payload_len = (dirlen - (current_pos * 1024));

		}

		//Set up the dir packet.
		myPacket dirpacket;
		bzero(&dirpacket, MY_PACKET_SIZE);
		dirpacket.seq_num = current_pos;
		dirpacket.num_packets = num_packets;
		dirpacket.pl_size = payload_len;

		//Fill the payload with the dirbuffer (or a piece thereof).
		memcpy(dirpacket.payload, dirbuffer + (current_pos * 1024) , payload_len);		

		//Send the dirpacket of the current sequence number.
		sendPacket(dirpacket, to);

		//Wait for a response or timeout.
		int ret = select(sock + 1, &fds, 0, 0, &tv);
		if (ret == -1) {

		    //Error check select.
		    perror("Select failure:");
		    exit(1);

		} else if (FD_ISSET(sock, &fds)) {

		    //Get the ready packet from the socket.
		    int len = recvfrom(sock, buf, MY_PACKET_SIZE, 0, (sockaddr *)&from, &from_len);	

		    //Cast the buffer into a myPacket struct.
		    myPacket *recv_packet = (myPacket *)buf;
		    uint8_t flags = recv_packet->flags;

		    //Check the packet for corruption.
		    if( !checkMD5(recv_packet) ) {

			//Ack is corrupt. Drop it.
		    } else if( (flags & DIR_REQ) >> 4 ) {

			sendPacket(ack, to);

		    } else if ( (flags & ACK) ) {

			//We got an ack, send the next packet.
			current_pos++;
		    } else {
			//This is not relevant, drop it.
		    }

		} else {
		    //Timeout. Resend the dir packet at the current sequence num.
		    cout << "Dir timeout. Resending..." << endl;
		    resends++;

		    //If we don't get an ack after 20 resends, just drop out and move on.
		    if ( resends > MAX_RESENDS) {
			current_pos++;
			resends = 0;
		    }

		}
	}

	closedir(DirectoryPointer);

}


void sendFile(char* filename, sockaddr *to) {

    //Struct
    packet_entry* packet_list;

    //Create structs for select.
    fd_set fds;
    struct timeval tv;

    //Struct to fill in from recvfrom()
    struct sockaddr_in from; 
    unsigned int from_len;

    int total_packets = numberOfPackets(filename);

    packet_list = new packet_entry[total_packets];
    bzero(packet_list, total_packets * sizeof(struct packet_entry));
	

    //Start the sliding window at 0.
    int window_pos = 0;
    int window_size = calculateWindowSize(total_packets);
	
    //Bitmap that keeps track of acks recieved.
    int * bitmap;
	bitmap = (int *)malloc(total_packets * sizeof(int));
    bzero(bitmap, total_packets * sizeof(int));

    //Buffer to hold data from the socket.
    char buf[MY_PACKET_SIZE];

    //Open the file for reading.
    int file_fd = open(filename, O_RDONLY);
    if (file_fd == -1) {
	perror("File open went wrong");
	exit(1);
    }


    //Selective repeat (send side).
    while(1) {

	for(int i = window_pos; ((i < window_size + window_pos) && (i < total_packets)); i++) {
	
	usleep(50);
	
		if (bitmap[i] == 0) {

            //cout << "WASSENT is this before if: " << packet_list[i].wasSent << endl;
		    //Check if the packet is in the deque.
			if ( packet_list[i].wasSent == 1) {
            
				//check timeout
				timeval stamp, current;
				long elapsed_sec, elapsed_usec, elapsed_msec;

				packet_entry entry = packet_list[i];
				stamp = entry.timestamp;
				gettimeofday(&current, 0);

				//Calculate the time differential between the stamp and the current time.
				elapsed_sec  = current.tv_sec  - stamp.tv_sec;
				elapsed_usec = current.tv_usec - stamp.tv_usec;

				//Get the total time elapsed in milliseconds.
				elapsed_msec = ((elapsed_sec) * 1000 + elapsed_usec/1000.0) + 0.5;

				//cout << "Elapsed time: " << elapsed_msec << endl;
				
				//Check for a timeout.
				if( elapsed_msec > TIMEOUT) {

				    //Resend the timedout packet.
				    cout << "Packet " << i << " timed out. Resending...." << endl;
                    cout << "Packet " << i << " has resent? " << packet_list[i].resends << endl;
				    sendPacket(entry.packet, to);
				    packet_list[i].timestamp = current;
                    packet_list[i].resends = packet_list[i].resends + 1;
                    
                    if(packet_list[i].resends >= MAX_RESENDS) {
                        bitmap[i] = 1;
                       
                    }
                  
				}
			}
		
			else {
				
				//Send the packet.
				myPacket send_packet = generatePacket(file_fd, i,total_packets, SEND);

				//cout << "Sending packet (seq number) " << i << " of " << total_packets << endl;

				//Send the packet off.
				sendPacket(send_packet, to);
				
				packet_entry list_entry;

				//Generate a timestamp;
				timeval start;
				gettimeofday(&start, 0);

				//Fill list entry struct with the packet, timestamp, and sequence num.
				list_entry.packet = send_packet;
				list_entry.timestamp = start;
                list_entry.wasSent = 1;
                list_entry.resends = 0;

				//Add to deque with a timestamp.
				packet_list[i] = list_entry;
				
			}
           
		}
		//Don't do anything if the bitmap[i]=1. That means we got an ACK.
	}
	
    while(bitmap[window_pos] == 1) {
        window_pos ++;
        if(window_pos >= total_packets)
            break;
    }
	
    cout << "Window pos is " << window_pos << " of " << total_packets << endl;
    cout << "Bitmap here is: " << bitmap[window_pos] << endl;
    //Quit if the bitmap is all 1's
    if(window_pos == (total_packets)) {
        break;
    }
    
	//Set the timeout to x microseconds.
	tv.tv_sec = 0;
	tv.tv_usec = 50000;

	//Add the sock FD to the fd set for select.
	FD_ZERO(&fds);
	FD_SET(sock, &fds);

	//Wait for a response or timeout.
	int ret = select(sock + 1, &fds, 0, 0, &tv);
	if (ret == -1) {

	    //Error check select.
	    perror("Select failure:");
	    exit(1);

	} else if (FD_ISSET(sock, &fds)) {

	    //Get the ready packet from the socket.
	    int len = recvfrom(sock, buf, MY_PACKET_SIZE, 0, (sockaddr *)&from, &from_len);

	    //Cast the buffer into a myPacket struct.
	    myPacket *recv_packet = (myPacket *)buf;

	    //Get some info from the packet.
	    int sequence_num = (int)recv_packet->seq_num;
	    int payload_size = (int)recv_packet->pl_size;
	    int flags = (int)recv_packet->flags;

	    if( !checkMD5(recv_packet) ) {
            cout << "Ack packet " << sequence_num << " is corrupt. Dropping it..." << endl;
	    } else if( ((flags & INIT) >> 7) ) {

            //Resend the ack for the init handshake because it must have gotten lost.
            //Copy the incoming and flip on the ACK bit to make an ack packet to send back.
            myPacket ack = *recv_packet;
            ack.flags = (ack.flags | ACK);
            sendPacket(ack, to);

	    } else if ( (sequence_num >= window_pos) && (sequence_num < (window_pos + window_size))) {

            //Received an ack in the window.
            bitmap[sequence_num] = 1;

            cout << "Received ack: " << sequence_num  << endl;

            //Remove from deque.
            //removeFromDeque(sequence_num);

            //Advance the window.
            //window_pos = advanceWindow(bitmap, total_packets, window_pos, window_size);
            while(bitmap[window_pos] == 1) {
                window_pos ++;
                if(window_pos >= total_packets)
                    break;
            }
            

	    } else {
            //Late acknowledgement. Do nothing.

	    }

	    if( window_pos >= total_packets) {
            //Transfer is done.
            break;
	    }


	} else {
	    //timeout.
	}
    }

    cout << "Transfer complete." << endl;

    // That's what you get for acting the mickey.
    delete [] packet_list;
    free(bitmap);
    
    close(file_fd);
    return;

}

void getFile(char* filename, sockaddr *to, int total_packets) {


    //LOL
    myPacket* packet_list = new myPacket[total_packets];
    bzero(packet_list, total_packets * sizeof(struct myPacket));

    cout << total_packets << endl;
	
	//Bitmap that keeps track of packets recieved.
    int * bitmap;
	bitmap = (int *)malloc(total_packets * sizeof(int));
    bzero(bitmap, total_packets * sizeof(int));

    //Open the file. Create it if it doesn't exist or destroy it if it already does.
    int file_fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0777);
    if (file_fd == -1) {
	perror("File open went wrong");
	exit(1);
    }

    //Start the sliding window at 0.
    int window_pos = 0;
    int window_size = calculateWindowSize(total_packets);

    //Struct to fill in from recvfrom()
    struct sockaddr_in from; 
    unsigned int from_len;

    //Buffer to hold data from the socket.
    char buf[MY_PACKET_SIZE];

    //Selective repeat (receive side).
    while(1) {

        int len = recvfrom(sock, buf, MY_PACKET_SIZE, 0, (sockaddr *)&from, &from_len);

        //Cast the buffer into a myPacket struct.
        myPacket *recv_packet = (myPacket *)buf;

        int sequence_num = (int)recv_packet->seq_num;
        int payload_size = (int)recv_packet->pl_size;

        //Check the packet for corruption.
        if( !checkMD5(recv_packet) ) {

            cout << "Packet " << sequence_num << " is corrupt. Dropping it..." << endl;
        } else if ( (sequence_num >= window_pos) && (sequence_num < (window_pos + window_size))) {



            //ACK
            myPacket ack = *recv_packet;
            ack.flags = (ack.flags | ACK);
           
            sendPacket(ack, to);
     
            //Set the corresponding bit to 1 in the bitmap.
            bitmap[sequence_num] = 1;

	    packet_list[sequence_num] = *recv_packet;
            
            cout << "Window pos before: " << window_pos << endl;
            
            //Move window up to the next empty slot.
            while(bitmap[window_pos] == 1) {
                window_pos ++;
                if(window_pos >= total_packets)
                    break;
            }
            cout << "Window pos after: " << window_pos << endl;
            
        } else {
    
            //Send an ACK for out of sequence packet and drop it.
            myPacket ack = *recv_packet;
            ack.flags = (ack.flags | ACK);
           
            sendPacket(ack, to);

        }	    

        //Check if the window is at the end.
        if (window_pos >= total_packets) {

            cout << "Transfer is complete! Window pos is " << window_pos << " total packets is" << total_packets << endl;
            //File transfer is done.
            break;
        }

    }

    cout << "Writing to file" << endl;

    for(int i = 0; i < total_packets; i++) {

	int write_ret = write(file_fd, packet_list[i].payload, packet_list[i].pl_size);
        if( write_ret <= 0) {
            perror("Write failed:");
            exit(1);
        }
    }

    
    
    close(file_fd);
    free(bitmap);
    
    return;

}




//This function handles setting up the connection with the client.
void initConnection(myPacket* packet, sockaddr *to) {

    //Check the packet for corruption.
    if( !checkMD5(packet) ) {

	cout << "Handshake request is corrupt." << endl;
	return; //Drop this packet and go back to the main loop.
    }

    //Copy the incoming and flip on the ACK bit to make an ack packet to send back.
    myPacket ack = *packet;
    ack.flags = (ack.flags | ACK);

    char *filename;

    int fnamesize = packet->pl_size;

    //Dynamically allocate a char array to store the filename using the payload size field.
    filename = new char[fnamesize];

    //Pull the filename out of the payload.
    strncpy(filename, (char *)(packet->payload), (size_t)(fnamesize));
    uint8_t flags = packet->flags;

    if ( ((flags & RECV) >> 6) ) {

        //The client wants a file.
	
	cout << "handshake" << filename << endl;

	ack.num_packets = numberOfPackets(filename);
	
	sendPacket(ack, to); //Send an ack to complete the handshake.
	sendFile(filename, to);

    } else if ( ((flags & SEND) >> 5) ) {

	//The client is sending a file.
	
	sendPacket(ack, to); //Send an ack to complete the handshake.
	getFile(filename, to, (int)packet->num_packets);

    } else {

	//Else return because this packet makes no sense.
	return;
    }

    //Free the allocated memory.
    delete [] filename;

    //After everything is done, return to the main loop in main :).
    return;
}

main(int argc, char *argv[]) {

	int len;	
	struct sockaddr_in me, from; 
	unsigned int from_len;
	int portnum;	
	int ret;
	char buf[MY_PACKET_SIZE];
	char port[] = "13722";
	struct hostent* blocked;
	const char * blockName = "turing";

	from_len = sizeof(struct sockaddr_in);
	
	bzero(&from, sizeof(from));
	me.sin_family = AF_INET;
	me.sin_addr.s_addr = htonl(INADDR_ANY);
	sscanf(port, "%d", &portnum);
	me.sin_port = htons(portnum);
	cout << "SERVER:  Made address for port " << port << endl;

	sock = socket(AF_INET, SOCK_DGRAM, 0);
	ret = bind(sock, (struct sockaddr *)&me, sizeof(me));

	// For blocking Turing
	blocked = gethostbyname(blockName);
	char fromaddr[INET_ADDRSTRLEN];
        
	while(1) {

		//Get data from the socket.
		len = recvfrom(sock, buf, MY_PACKET_SIZE, 0, (sockaddr *)&from, &from_len);

		//Get a pointer to the sockaddr_in from struct (for passthrough to functions).
		sockaddr *from_ptr;
		from_ptr = (sockaddr *)&from;
		
		// Get the address of the connected client
		inet_ntop(AF_INET, &(from.sin_addr), fromaddr, INET_ADDRSTRLEN);
		cout << "CONNECTED ADDRESS " << fromaddr << endl;
		
		/* Compare the hostname of the client against the blocked computer's
		   list of hostnames. */
		for(int i=0; i++; blocked->h_addr_list[i] != NULL) {
			if (strcmp(blocked->h_addr_list[i], fromaddr)) {
				cout << "Blocked a lame client" << endl;
				continue; // Break here and drop the connection
			}
		}

		//Cast the data from the socket into myPacket struct.
		myPacket *packet = (myPacket *)buf;
		uint8_t flags = packet->flags;
		
		//Check if this is a handshake.
		if( ((flags & INIT) >> 7) ) {
		   
		   //Start the handshake if so. 
		    initConnection(packet, from_ptr);
		} else if ( (flags & DIR_REQ) >> 4) {

		    sendDir(packet, from_ptr);
		}

		
		
		//buf[len] = 0;
		//cout << "SERVER:  Client said:" << buf << endl;
		//strcat(buf, "Pooh on youu");
		//len = sendto(sock, buf, BUFSIZE, 0,(sockaddr *)&from, sizeof(from));
		//cout << "SERVER:  Wrote " << len << " bytes " << endl;
	}

}
